package com.faner4cloud.yun.service.impl;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.faner4cloud.plugin.excel.annotation.ResponseExcel;
import com.faner4cloud.plugin.excel.handler.SingleSheetWriteHandler;
import com.faner4cloud.plugin.oss.OssProperties;
import com.faner4cloud.plugin.oss.service.OssTemplate;
import com.faner4cloud.yun.common.constant.CacheConstants;
import com.faner4cloud.yun.common.util.ClassUtils;
import com.faner4cloud.yun.common.util.progress.Progress;
import com.faner4cloud.yun.common.util.progress.ProgressBuilder;
import com.faner4cloud.yun.common.util.progress.Result;
import com.faner4cloud.yun.common.util.progress.TaskBuilder;
import com.faner4cloud.yun.dao.entity.SysDict;
import com.faner4cloud.yun.service.ExportService;
import com.faner4cloud.yun.service.SysDictService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author ...zz
 * @version v1
 * @summary 导出案例
 * @since 2022/5/16 9:04 AM
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExportServiceImpl implements ExportService {
	private final SysDictService sysDictService;
	private final RedisTemplate redisTemplate;
	private final SingleSheetWriteHandler singleSheetWriteHandler;
	private final OssTemplate ossTemplate;
	private final OssProperties ossProperties;
	private final ExecutorService exportExecutor = new ThreadPoolExecutor(4, 4, 0L,
		TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100), new ThreadFactory() {
		private final AtomicInteger threadNumber = new AtomicInteger(1);

		@SuppressWarnings("NullableProblems")
		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, "export" + threadNumber.getAndIncrement());
		}
	});

	/**
	 * 异步轮询方式导出导入 可以有进度条的显示
	 * @param param
	 * @return {@link Result}
	 */
	@Override
	public Result asyncExport(String param) {
		String md5 = MD5.create().digestHex16(param);
		String sign = "async-export-" + md5;
		String key = CacheConstants.EXPORT_ASYNC +":" + md5;
		Result result = this.findResult(key);
		if (result != null) {
			return result;
		} else {
			Progress progress = ProgressBuilder.identity(sign).timing(TimeUnit.SECONDS, 2, p -> setResult(key, p.get())).tasks(
					TaskBuilder.prepare(5, "准备excel", task -> "准备excel")
						.next(50,"数据查询", (a,task) -> {
							AtomicInteger i = new AtomicInteger(0);
							List<SysDict> exportList = new ArrayList<>();
							List<SysDict> tmp;
							int pageNo = 1;
							int pageSize = 10;
							long total = sysDictService.count();
							while(total > 0 && (tmp = sysDictService.page(new Page<>(pageNo,pageSize)).getRecords()).size() > 0){
								tmp.forEach(t->{
									if (i.incrementAndGet() % 50 == 0) {
										task.mark(i.get(), total);
									}
								});
								exportList.addAll(tmp);
								pageNo++;
							}
							return exportList;
						})
						.next(40, "填充数据", (exportList, task) -> {
							ResponseExcel responseExcel = ClassUtils.getAnnotation(ClassUtils.getMethod(ExportService.class, "asyncExport",String.class,Boolean.class), ResponseExcel.class);
							// 输出 excel 数据到 输入流
							ByteArrayOutputStream os = new ByteArrayOutputStream();
							try {
								singleSheetWriteHandler.write(exportList, os, responseExcel);
							} catch (Exception e) {
								String error = "导出异常，excel输出stream失败， e=" + e.getMessage();
								log.error(error);
								throw new RuntimeException(error);
							}
							return os;
						})
						.next(5, "上传文件", (os, task) -> {
							String uploadUrl = "";
							try {
								uploadUrl = ossTemplate.putObject(ossProperties.getBucketName(), sign + ".xlsx", os, 60);
							} catch (IOException e) {
								String error = "导出异常，excel上传oss失败， e=" + e.getMessage();
								throw new RuntimeException(error);
							}
							log.info("上传文件到oss路径：{}", uploadUrl);
							return uploadUrl;
						})
						.build())
				.onError((p, t) -> {
					setResult(key, p.get());
					log.error("export异常， e=“{}”", t.getMessage());
				})
				.thenAccept(p -> setResult(key, p.get())).build();
			progress.run(exportExecutor);
			return progress.get();
		}
	}

	@Override
	public Result asyncExport(String param, Boolean a) {
		return null;
	}

	@Override
	public List<SysDict> syncExport() {
		return sysDictService.list();
	}

	/**
	 * 查找缓存是否命中
	 * @param key 前缀
	 * @return Result
	 */
	public Result findResult(String key) {
		String result = (String) redisTemplate.opsForValue().get(key);
		return result == null ? null : JSONUtil.parse(result).toBean(Result.class);
	}

	/**
	 * 设置 15分钟缓存
	 * @param key 前缀
	 * @param result Result
	 * @return
	 */
	public void setResult(String key, Result result) {
		redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(result), 15, TimeUnit.MINUTES);
	}
}
